package jp.syuriken.snsw.terenaibot;

import java.text.MessageFormat;
import java.util.logging.Level;
import java.util.regex.Matcher;

import jp.syuriken.snsw.terenaibot.format.FormatSyntaxException;
import jp.syuriken.snsw.terenaibot.format.PrecompiledSyntax;
import jp.syuriken.snsw.terenaibot.plugins.FriendshipHookPlugin;

/**
 * ポストラッパ。
 * 
 * <p>{@link PostWrapperPool}の処理の都合により、デフォルトコンストラクタを使用します。</p>
 * @author $Author: snswinhaiku.lo@gmail.com $
 * @version 0.3
 */
public abstract class PostWrapper2 extends PostWrapper {
	
	/**
	 * バックグラウンドでポストするためのジョブ。
	 * 
	 * @author $Author: snswinhaiku.lo@gmail.com $
	 */
	public final class PostJob implements WorkJob {
		
		private final PostJobType jobType;
		
		private final String poststr;
		
		private final Post2 inReplyTo;
		
		private final UserInfo userId;
		
		private final PrecompiledSyntax postFormat;
		
		private final Matcher matcher;
		

		/**
		 * インスタンスを生成する。
		 * 
		 * @param jobType ジョブ種別
		 * @param postFormat ポストフォーマット
		 * @param matcher 正規表現マッチ操作エンジン
		 * @param inReplyTo 返信先
		 * @see PostJob#PostJob(PostJobType, String)
		 * @see PostJob#PostJob(PostJobType, String, Post2)
		 * @see PostJob#PostJob(PostJobType, String, UserInfo)
		 * @see PostJob#PostJob(PostJobType, PrecompiledSyntax, Matcher, Post2)
		 * @see PostJob#PostJob(PostJobType, PrecompiledSyntax, Matcher, UserInfo)
		 */
		public PostJob(PostJobType jobType, PrecompiledSyntax postFormat, Matcher matcher, Post2 inReplyTo) {
			if ((jobType == PostJobType.QUOTE_FORMAT || jobType == PostJobType.REPLY_FORMAT) == false) {
				throw new RuntimeException("PostWrapper2#PostJob<init>: not (quote|reply)_format, but called PJT,S,M,P");
			}
			this.jobType = jobType;
			poststr = null;
			this.inReplyTo = inReplyTo;
			userId = null;
			this.postFormat = postFormat;
			this.matcher = matcher;
		}
		
		/**
		 * インスタンスを生成する。
		 * 
		 * @param jobType ジョブ種別
		 * @param postFormat ポストフォーマット
		 * @param matcher 正規表現マッチ操作エンジン
		 * @param userId コール先
		 * @see PostJob#PostJob(PostJobType, PrecompiledSyntax, Matcher, Post2)
		 * @see PostJob#PostJob(PostJobType, String)
		 * @see PostJob#PostJob(PostJobType, String, Post2)
		 * @see PostJob#PostJob(PostJobType, String, UserInfo)
		 * @see PostJob#PostJob(PostJobType, PrecompiledSyntax, Matcher, Post2)
		 */
		public PostJob(PostJobType jobType, PrecompiledSyntax postFormat, Matcher matcher, UserInfo userId) {
			if (jobType != PostJobType.CALL_FORMAT) {
				throw new RuntimeException("PostWrapper2#PostJob<init>: not call_format, but called PJT,S,M,UI");
			}
			this.jobType = jobType;
			poststr = null;
			this.postFormat = postFormat;
			this.matcher = matcher;
			inReplyTo = null;
			this.userId = userId;
		}
		
		/**
		 * インスタンスを生成する。
		 * 
		 * @param jobType ジョブ種別
		 * @param poststr ポストする文字列
		 * @see PostJob#PostJob(PostJobType, PrecompiledSyntax, Matcher, Post2)
		 * @see PostJob#PostJob(PostJobType, String, Post2)
		 * @see PostJob#PostJob(PostJobType, String, UserInfo)
		 * @see PostJob#PostJob(PostJobType, PrecompiledSyntax, Matcher, Post2)
		 * @see PostJob#PostJob(PostJobType, PrecompiledSyntax, Matcher, UserInfo)
		 */
		public PostJob(PostJobType jobType, String poststr) {
			this.jobType = jobType;
			this.poststr = poststr;
			inReplyTo = null;
			userId = null;
			postFormat = null;
			matcher = null;
		}
		
		/**
		 * インスタンスを生成する。
		 * 
		 * @param jobType ジョブ種別
		 * @param poststr ポストする文字列
		 * @param inReplyTo 返信先
		 * @see PostJob#PostJob(PostJobType, PrecompiledSyntax, Matcher, Post2)
		 * @see PostJob#PostJob(PostJobType, String)
		 * @see PostJob#PostJob(PostJobType, String, UserInfo)
		 * @see PostJob#PostJob(PostJobType, PrecompiledSyntax, Matcher, Post2)
		 * @see PostJob#PostJob(PostJobType, PrecompiledSyntax, Matcher, UserInfo)
		 */
		public PostJob(PostJobType jobType, String poststr, Post2 inReplyTo) {
			if ((jobType == PostJobType.QUOTE || jobType == PostJobType.REPLY) == false) {
				throw new RuntimeException("PostWrapper2#PostJob<init>: not (quote|reply), but called PJT,S,P");
			}
			this.jobType = jobType;
			this.poststr = poststr;
			this.inReplyTo = inReplyTo;
			userId = null;
			postFormat = null;
			matcher = null;
		}
		
		/**
		 * インスタンスを生成する。
		 * 
		 * @param jobType ジョブ種別
		 * @param poststr ポストする文字列
		 * @param userId コール先
		 * @see PostJob#PostJob(PostJobType, PrecompiledSyntax, Matcher, Post2)
		 * @see PostJob#PostJob(PostJobType, String)
		 * @see PostJob#PostJob(PostJobType, String, Post2)
		 * @see PostJob#PostJob(PostJobType, PrecompiledSyntax, Matcher, Post2)
		 * @see PostJob#PostJob(PostJobType, PrecompiledSyntax, Matcher, UserInfo)
		 */
		public PostJob(PostJobType jobType, String poststr, UserInfo userId) {
			if (jobType != PostJobType.CALL) {
				throw new RuntimeException("PostWrapper2#PostJob<init>: not call_format, but called PJT,S,UI");
			}
			
			this.jobType = jobType;
			this.poststr = poststr;
			inReplyTo = null;
			this.userId = userId;
			postFormat = null;
			matcher = null;
		}
		
		@Override
		public void work() {
			try {
				switch (jobType) {
					case DEFAULT:
						post(poststr);
						break;
					case CALL:
						PostWrapper2.this.postInUserCall(poststr, userId);
						break;
					case REPLY:
						PostWrapper2.this.postInReply(poststr, inReplyTo);
						break;
					case QUOTE:
						PostWrapper2.this.postInQuote(poststr, inReplyTo);
						break;
					case CALL_FORMAT:
						PostWrapper2.this.postInUserCall(postFormat, matcher, userId);
						break;
					case REPLY_FORMAT:
						PostWrapper2.this.postInReply(postFormat, matcher, inReplyTo);
						break;
					case QUOTE_FORMAT:
						PostWrapper2.this.postInQuote(postFormat, matcher, inReplyTo);
						break;
					default:
						break;
				}
			} catch (IllegalStateException e) {
				botConfiguration.getLogger().log(Level.WARNING, e);
			} catch (WrapperException2 e) {
				botConfiguration.getLogger().log(Level.WARNING, e);
			} catch (FormatSyntaxException e) {
				botConfiguration.getLogger().log(Level.WARNING, e);
			}
		}
	}
	
	private enum PostJobType {
		DEFAULT, CALL, CALL_FORMAT, REPLY, REPLY_FORMAT, QUOTE, QUOTE_FORMAT;
	}
	
	/**
	 * プライベートメッセージをバックグラウンドで送信するジョブ。
	 * 
	 * @author $Author: snswinhaiku.lo@gmail.com $
	 */
	public class PrivateMessageJob implements WorkJob {
		
		private final String pmString;
		
		private final String userId;
		

		/**
		 * インスタンスを生成する。
		 * 
		 * @param pmString プライベートメッセージの内容
		 * @param userId 送信先ID
		 */
		public PrivateMessageJob(String pmString, String userId) {
			this.pmString = pmString;
			this.userId = userId;
		}
		
		@Override
		public String toString() {
			return MessageFormat.format("PrivateMessageJob: (to {0}) {1}", userId, pmString);
		}
		
		@Override
		public void work() {
			try {
				sendPrivateMessage(pmString, userId);
			} catch (WrapperException2 e) {
				botConfiguration.getLogger().log(Level.WARNING, e);
			}
		}
	}
	

	/**
	 * インスタンスを生成する。
	 * 
	 */
	protected PostWrapper2() {
	}
	
	/**
	 * APIコール規制がかかったかどうか。trueが返ってきてもAPIコールが出来る可能性があります。
	 * @return APIコール規制中かどうか
	 */
	public abstract boolean exceedApiLimitation();
	
	/**
	 * ポスト規制がかかったかどうか。
	 * 
	 * @return ポスト規制中かどうか
	 */
	public abstract boolean exceedPostLimitation();
	
	/**
	 * フォローを行う。必ずしもフォローが行われるわけではありません。
	 * 
	 * @param userId ユーザー名
	 * @return フォローしたかどうか
	 * @throws WrapperException2 フォロー中に例外が発生した
	 * @see #follow(UserInfo)
	 * @see FriendshipHookPlugin#hookBeforeFollow(String)
	 */
	public abstract boolean follow(String userId) throws WrapperException2;
	
	/**
	 * フォローを行う。必ずしもフォローが行われるわけではありません。
	 * 
	 * <p>この関数の呼び出しは {@link #follow(String)} の第一引数に
	 * userInfo.getUserId() を指定した時と同じ挙動を示します。</p>
	 * @param userInfo ユーザー情報
	 * @return フォローしたかどうか
	 * @throws WrapperException2 フォロー中に例外が発生した
	 * @see #follow(String)
	 * @see FriendshipHookPlugin#hookBeforeFollow(String)
	 */
	public boolean follow(UserInfo userInfo) throws WrapperException2 {
		return follow(userInfo.getUserId());
	}
	
	/**
	 * フォロワーを取得する。これは getFollowers(false) を呼び出した時と同じ挙動を示します。
	 * 
	 * @return フォロワーのリスト
	 * @throws WrapperException2 取得中に例外が発生した
	 * @see #getFollowers(boolean)
	 */
	public final UserInfo[] getFollowers() throws WrapperException2 {
		return getFollowers(false);
	}
	
	/**
	 * フォロワーを取得する。
	 * 
	 * <p>パラメータ doCleanCache には、必ずしも最新の情報が必要でないときは false を指定するべきです。</p>
	 * 
	 * @param doCleanCache 新しい情報を取得するかどうか
	 * @return フォロワーのリスト
	 * @throws WrapperException2 フォロワーの取得中に例外が発生した
	 * @see #getFollowers()
	 */
	public abstract UserInfo[] getFollowers(boolean doCleanCache) throws WrapperException2;
	
	/**
	 * フォローを取得する。これは getFollowing(false) を呼び出した時と同じ挙動を示します。
	 * 
	 * @return フォローのリスト
	 * @throws WrapperException2 取得中に例外が発生した
	 * @see #getFollowing(boolean)
	 */
	public final UserInfo[] getFollowing() throws WrapperException2 {
		return getFollowing(false);
	}
	
	/**
	 * フォローを取得する。
	 * 
	 * <p>パラメータ doCleanCache には、必ずしも最新の情報が必要でないときは false を指定するべきです。</p>
	 * 
	 * @param doCleanCache 新しい情報を取得するかどうか
	 * @return フォローのリスト
	 * @throws WrapperException2 フォローの取得中に例外が発生した
	 * @see #getFollowing()
	 */
	public abstract UserInfo[] getFollowing(boolean doCleanCache) throws WrapperException2;
	
	/**
	 * ユーザーコールがかかったポストを取得する。
	 * 
	 * @return ユーザーコールポスト
	 * @throws WrapperException2 API呼び出し中に例外が発生した
	 */
	public abstract Post2[] getMentions() throws WrapperException2;
	
	/**
	 * ポストを取得する
	 * 
	 * @param postId ポストID 
	 * @return 取得されたポスト。{@link NullPost}に注意。
	 * @throws WrapperException2 ラッパ例外
	 * @throws IllegalStateException 正しくないインスタンス状態
	 */
	@Override
	public abstract Post2 getPost(String postId) throws IllegalStateException, WrapperException2;
	
	/**
	 * 実際にポストするクラスを取得
	 * 
	 * @return ポストクラス
	 */
	@Override
	public abstract Object getPostActionClass();
	
	/**
	 * プライベートメッセージを取得する。
	 * 
	 * @return プライベートメッセージのリスト
	 * @throws WrapperException2 API呼び出し中に例外が発生した
	 */
	public abstract Post2[] getPrivateMessage() throws WrapperException2;
	
	/**
	 * タイムラインを取得する。
	 * 
	 * @return タイムライン
	 * @throws WrapperException2 API呼び出し中に例外が発生した
	 */
	public abstract Post2[] getTimeline() throws WrapperException2;
	
	/**
	 * 自分のユーザー情報を取得する。
	 * @return ユーザー情報
	 * @throws WrapperException2 API呼び出し中に例外が発生した
	 * @see #getUserInfo(String)
	 */
	public abstract UserInfo getUserInfo() throws WrapperException2;
	
	/**
	 * ユーザー情報を取得する。
	 * 
	 * @param userId ユーザー名
	 * @return ユーザー情報
	 * @throws WrapperException2 API呼び出し中に例外が発生した
	 * @see #getUserInfo()
	 */
	public abstract UserInfo getUserInfo(String userId) throws WrapperException2;
	
	/**
	 * ユーザー名を取得する。ユニークでかつユーザフレンドリーな名前を返すべきです。
	 * 
	 * @return ユーザー名
	 * @throws WrapperException2 WrapperException2。詳細は{@link WrapperException2#getCause()}を用いてください。
	 * @throws IllegalStateException ログインしていない？
	 */
	@Override
	public abstract String getUserName() throws IllegalStateException, WrapperException2;
	
	/**
	 * ラッパ名。これは通常クラス名です。パッケージ名を返す必要はありません。
	 * 
	 * @return ラッパ名
	 */
	@Override
	public abstract String getWrapperName();
	
	/**
	 * 初期化する。
	 * 
	 * @param configuration 設定
	 * @return 初期化済みの{@link PostWrapper2}
	 * @throws ConfigurationException 設定が出来なかった
	 */
	@Override
	public PostWrapper2 init(BotConfiguration configuration) throws ConfigurationException {
		botConfiguration = configuration;
		return this;
	}
	
	/**
	 * ポストを行う。
	 * 
	 * @param poststr ポストする文字列
	 * @return 送信したポスト。nullが返る場合もあり。
	 * @throws WrapperException2 ラッパ例外
	 * @throws IllegalStateException インスタンスが正しくない状態
	 * @see #postBackground(String)
	 */
	@Override
	public abstract Post2 post(String poststr) throws IllegalStateException, WrapperException2;
	
	/**
	 * バックグラウンドでポストを行う。
	 * 
	 * @param poststr ポストする文字列
	 */
	public void postBackground(String poststr) {
		botConfiguration.getJobQueue().addJob(new PostJob(PostJobType.DEFAULT, poststr));
	}
	
	/**
	 * バックグラウンドで引用してポストする。これはフォーマットされます。
	 * 
	 * @param postFormat 被フォーマット文字列
	 * @param matcher 引用元のこのポストをする原因となった正規表現
	 * @param inReplyTo 引用元
	 * @see #postInQuote(PrecompiledSyntax, Matcher, Post2)
	 */
	public void postBackgroundInQuote(PrecompiledSyntax postFormat, Matcher matcher, Post2 inReplyTo) {
		botConfiguration.getJobQueue().addJob(new PostJob(PostJobType.QUOTE_FORMAT, postFormat, matcher, inReplyTo));
	}
	
	/**
	 * 引用してポストする。これはフォーマットされません。
	 * 
	 * @param postString ポストする文字列。
	 * @param inReplyTo 引用元
	 * @see #postInQuote(String, Post2)
	 */
	public void postBackgroundInQuote(String postString, Post2 inReplyTo) {
		botConfiguration.getJobQueue().addJob(new PostJob(PostJobType.QUOTE, postString, inReplyTo));
	}
	
	/**
	 * 返信する。これはフォーマットされます。
	 * 
	 * @param postFormat 被フォーマット文字列
	 * @param matcher 返信先のこのポストをする原因となった正規表現
	 * @param inReplyTo 返信先
	 * @see #postInReply(PrecompiledSyntax, Matcher, Post2)
	 */
	public void postBackgroundInReply(PrecompiledSyntax postFormat, Matcher matcher, Post2 inReplyTo) {
		botConfiguration.getJobQueue().addJob(new PostJob(PostJobType.REPLY_FORMAT, postFormat, matcher, inReplyTo));
	}
	
	/**
	 * バックグラウンドで返信してポストする。これはフォーマットされません。
	 * @param postString ポストする文字列
	 * @param inReplyTo 引用元
	 * @see #postInReply(String, Post2)
	 */
	public void postBackgroundInReply(String postString, Post2 inReplyTo) {
		botConfiguration.getJobQueue().addJob(new PostJob(PostJobType.REPLY, postString, inReplyTo));
	}
	
	/**
	 * バックグラウンドでユーザーコールしてポストする。これはフォーマットされます。
	 * 
	 * @param postFormat 被フォーマット文字列
	 * @param matcher ユーザーコールする原因となった正規表現
	 * @param userId ユーザーID
	 * @see #postInQuote(String, Post2)
	 */
	public void postBackgroundInUserCall(PrecompiledSyntax postFormat, Matcher matcher, UserInfo userId) {
		botConfiguration.getJobQueue().addJob(new PostJob(PostJobType.CALL_FORMAT, postFormat, matcher, userId));
	}
	
	/**
	 * ユーザーコールしてポストする。これはフォーマットされません。
	 * 
	 * @param postString ポストする文字列
	 * @param userId ユーザーID
	 * @see #postBackgroundInUserCall(String, UserInfo)
	 */
	public void postBackgroundInUserCall(String postString, UserInfo userId) {
		botConfiguration.getJobQueue().addJob(new PostJob(PostJobType.CALL, postString, userId));
	}
	
	/**
	 * 引用してポストする。これはフォーマットされます。
	 * 
	 * @param postFormat 被フォーマット文字列
	 * @param matcher 引用元のこのポストをする原因となった正規表現
	 * @param inReplyTo 引用元
	 * @return ポスト。 {@link NullPost2}が返る可能性があります。
	 * @throws IllegalStateException インスタンスが正しくない状態
	 * @throws WrapperException2 API呼び出し中に例外が発生した
	 * @throws FormatSyntaxException フォーマット中にエラーが発生しました
	 * @see #postInQuote(String, Post2)
	 * @see #postBackgroundInQuote(PrecompiledSyntax, Matcher, Post2)
	 */
	public abstract Post2 postInQuote(PrecompiledSyntax postFormat, Matcher matcher, Post2 inReplyTo)
			throws IllegalStateException, WrapperException2, FormatSyntaxException;
	
	/**
	 * 引用してポストする。これはフォーマットされません。
	 * 
	 * @param postString ポストする文字列。
	 * @param inReplyTo 引用元
	 * @return ポスト。 {@link NullPost2}が返る可能性があります。
	 * @throws IllegalStateException インスタンスが正しくない状態
	 * @throws WrapperException2 API呼び出し中に例外が発生した
	 * @see #postInQuote(PrecompiledSyntax, Matcher, Post2)
	 * @see #postBackgroundInQuote(String, Post2)
	 */
	public abstract Post2 postInQuote(String postString, Post2 inReplyTo) throws IllegalStateException,
			WrapperException2;
	
	/**
	 * 返信する。これはフォーマットされます。
	 * 
	 * @param postFormat 被フォーマット文字列
	 * @param matcher 返信先のこのポストをする原因となった正規表現
	 * @param inReplyTo 返信先
	 * @return ポスト。 {@link NullPost2}が返る可能性があります。
	 * @throws IllegalStateException インスタンスが正しくない状態
	 * @throws WrapperException2 API呼び出し中に例外が発生した
	 * @throws FormatSyntaxException フォーマット中にエラーが発生しました
	 * @see #postInReply(String, Post2)
	 * @see #postBackgroundInReply(PrecompiledSyntax, Matcher, Post2)
	 */
	public abstract Post2 postInReply(PrecompiledSyntax postFormat, Matcher matcher, Post2 inReplyTo)
			throws IllegalStateException, WrapperException2, FormatSyntaxException;
	
	/**
	 * 返信してポストする。これはフォーマットされません。
	 * @param postString ポストする文字列
	 * @param inReplyTo 引用元
	 * @return ポスト。 {@link NullPost2}が返る可能性があります。
	 * @throws IllegalStateException インスタンスが正しくない状態
	 * @throws WrapperException2 API呼び出し中に例外が発生した
	 * @see #postInReply(PrecompiledSyntax, Matcher, Post2)
	 * @see #postBackgroundInReply(String, Post2)
	 */
	public abstract Post2 postInReply(String postString, Post2 inReplyTo) throws IllegalStateException,
			WrapperException2;
	
	/**
	 * ユーザーコールしてポストする。これはフォーマットされます。
	 * 
	 * @param postFormat 被フォーマット文字列
	 * @param matcher ユーザーコールする原因となった正規表現
	 * @param userId ユーザーID
	 * @return ポスト。 {@link NullPost2}が返る可能性があります。
	 * @throws IllegalStateException インスタンスが正しくない状態
	 * @throws FormatSyntaxException フォーマット中にエラーが発生しました
	 * @throws WrapperException2 API呼び出し中に例外が発生した
	 * @see #postInQuote(String, Post2)
	 * @see #postBackgroundInQuote(PrecompiledSyntax, Matcher, Post2)
	 */
	public abstract Post2 postInUserCall(PrecompiledSyntax postFormat, Matcher matcher, UserInfo userId)
			throws IllegalStateException, FormatSyntaxException, WrapperException2;
	
	/**
	 * ユーザーコールしてポストする。これはフォーマットされません。
	 * 
	 * @param postString ポストする文字列
	 * @param userId ユーザーID
	 * @return ポスト。 {@link NullPost2}が返る可能性があります。
	 * @throws IllegalStateException インスタンスが正しくない状態
	 * @throws WrapperException2 API呼び出し中に例外が発生した
	 * @see #postInUserCall(PrecompiledSyntax, Matcher, UserInfo)
	 * @see #postBackgroundInUserCall(String, UserInfo)
	 */
	public abstract Post2 postInUserCall(String postString, UserInfo userId) throws IllegalStateException,
			WrapperException2;
	
	/**
	 * プライベートメッセージを送信する。
	 * 
	 * @param pmString メッセージの内容
	 * @param userId ユーザーID
	 * @return ポスト。 {@link NullPost2}が返る可能性があります。
	 * @throws WrapperException2 API呼び出し中に例外が発生した
	 * @see #sendPrivateMessageBackground(String, String)
	 */
	public abstract Post2 sendPrivateMessage(String pmString, String userId) throws WrapperException2;
	
	/**
	 * バックグラウンドでプライベートメッセージを送信する。
	 * 
	 * @param pmString メッセージの内容
	 * @param userId ユーザーID
	 * @see #sendPrivateMessage(String, String)
	 */
	public void sendPrivateMessageBackground(String pmString, String userId) {
		botConfiguration.getJobQueue().addJob(new PrivateMessageJob(pmString, userId));
	}
}
